﻿// 2025/10/17: 首个版本

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Avalonia.Controls;
using Avalonia.Input;
using ASEva;
using ASEva.UIAvalonia;
using ASEva.Utility;
using System.Threading.Tasks;

namespace CommonSource
{
    partial class XmlEditor : DialogPanel
    {
        public XmlEditor() // For designer
        {
            InitializeComponent();
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
        }

        public XmlEditor(String xmlString, String iconResourceName = null)
        {
            InitializeComponent();
            SetResizableMode(700, 400, 900, 600);
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
            Icon = ResourceLoader.Load(iconResourceName)?.ToAvaloniaWindowIcon();
            Title = language["title"];
            DataContext = xmlString;

            var xml = Xml.LoadString(xmlString);
            if (xml == null) xml = Xml.Create("root");

            originalXmlString = xmlString;
            model = new XmlEditorViewModel(xml, language, iconResourceName);
            model.InitializeNodeItems();
            DataContext = model;

            model.CloseRequested += delegate
            {
                BoolResult = model.Confirmed;
                Close();
            };
        }

        public String XmlString
        {
            get
            {
                var xml = model.Confirmed ? model.Xml : Xml.LoadString(originalXmlString);
                if (xml == null) xml = Xml.Create("root");

                var memoryStream = new MemoryStream();

                var settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.IndentChars = "    ";

                var xmlWriter = XmlWriter.Create(memoryStream, settings);
                xml.Save(xmlWriter);
                xmlWriter.Close();

                memoryStream.Position = 3; // BOM
                var data = new byte[memoryStream.Length - 3];
                memoryStream.Read(data, 0, (int)memoryStream.Length - 3);
                memoryStream.Close();

                return Encoding.UTF8.GetString(data);
            }
        }

        private void childNodeItem_PointerPressed(object sender, PointerPressedEventArgs e)
        {
            if (e.ClickCount == 2) model.GoTo();
        }

        private LanguageSwitch language;
        private XmlEditorViewModel model;
        private String originalXmlString;
    }

    partial class XmlEditorViewModel(XmlDocument xml, LanguageSwitch language, String iconResourceName) : ObservableObject
    {
        // Types /////////////////////////////////////////////////////////////////////////////////
        /// 
        public partial class XmlNodeItem(XmlElement elem, XmlNodeItem parentItem) : ObservableObject
        {
            public String Name => elem.Name;

            public XmlElement Element => elem;

            public XmlNodeItem ParentItem => parentItem;

            public XmlNodeItem[] ChildItems
            {
                get
                {
                    if (childItems == null)
                    {
                        var childItems = new List<XmlNodeItem>();
                        foreach (var child in elem.ChildNodes)
                        {
                            if (child is XmlElement) childItems.Add(new XmlNodeItem(child as XmlElement, this));
                        }
                        this.childItems = childItems.ToArray();
                    }
                    return childItems;
                }
            }

            public XmlAttributeItem[] AttributeItems
            {
                get
                {
                    if (attributeItems == null)
                    {
                        var attributeItems = new List<XmlAttributeItem>();
                        foreach (var attribute in elem.Attributes)
                        {
                            attributeItems.Add(new XmlAttributeItem(attribute as XmlAttribute));
                        }
                        this.attributeItems = attributeItems.ToArray();
                    }
                    return attributeItems;
                }
            }

            public int RemoveChild(XmlNodeItem childItem)
            {
                if (!ChildItems.Contains(childItem)) return -1;
                int index = ChildItems.ToList().IndexOf(childItem);
                elem.RemoveChild(childItem.Element);
                childItems = childItems.ToList().Where(item => item != childItem).ToArray();
                return Math.Min(index, childItems.Length - 1);
            }

            public int DuplicateChild(XmlNodeItem childItem)
            {
                if (!ChildItems.Contains(childItem)) return -1;
                var newChild = childItem.Element.CloneNode(true) as XmlElement;
                elem.AppendChild(newChild);
                var newList = childItems.ToList();
                newList.Add(new XmlNodeItem(newChild, this));
                childItems = newList.ToArray();
                return newList.Count - 1;
            }

            private XmlNodeItem[] childItems = null;
            private XmlAttributeItem[] attributeItems = null;
        }

        public partial class XmlAttributeItem(XmlAttribute attr) : ObservableObject
        {
            public String Key => attr.Name;

            [ObservableProperty]
            private String value = attr.Value;

            partial void OnValueChanged(String value)
            {
                attr.Value = value;
            }
        }

        // Parent node ///////////////////////////////////////////////////////////////////////////

        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(ParentNodeItemSafeName))]
        [NotifyPropertyChangedFor(nameof(CanRemoveOrDuplicate))]
        private XmlNodeItem parentNodeItem = null;

        public String ParentNodeItemSafeName => ParentNodeItem?.Name ?? language["none"];

        [RelayCommand]
        private void GoBack()
        {
            if (ParentNodeItem == null) return;
            var targetItem = ParentNodeItem;
            if (targetItem.ParentItem == null) NodeItems = [targetItem];
            else NodeItems = targetItem.ParentItem.ChildItems;
            SelectedNodeItem = targetItem;
        }

        // Node items ////////////////////////////////////////////////////////////////////////////

        [ObservableProperty]
        private XmlNodeItem[] nodeItems = [];

        public void InitializeNodeItems()
        {
            NodeItems = [new XmlNodeItem(Xml.DocumentElement, null)];
        }

        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(CanRemoveOrDuplicate))]
        [NotifyPropertyChangedFor(nameof(CanEditContent))]
        private XmlNodeItem selectedNodeItem = null;

        partial void OnSelectedNodeItemChanged(XmlNodeItem value)
        {
            if (value == null)
            {
                ParentNodeItem = null;
                ChildNodeItems = [];
                AttributeItems = [];
                Content = "";
                return;
            }
            else
            {
                ParentNodeItem = value.ParentItem;
                ChildNodeItems = value.ChildItems;
                AttributeItems = value.AttributeItems;
                if (ChildNodeItems.Length == 0) Content = value.Element.InnerText;
                else Content = "";
            }
        }

        public bool CanRemoveOrDuplicate => SelectedNodeItem != null && ParentNodeItem != null;

        [RelayCommand]
        private void RemoveNode()
        {
            if (ParentNodeItem == null) return;
            var index = ParentNodeItem.RemoveChild(SelectedNodeItem);
            if (index < 0) GoBack();
            else
            {
                NodeItems = ParentNodeItem.ChildItems;
                SelectedNodeItem = NodeItems[index];
            }
        }

        [RelayCommand]
        private void DuplicateNode()
        {
            if (ParentNodeItem == null) return;
            var index = ParentNodeItem.DuplicateChild(SelectedNodeItem);
            NodeItems = ParentNodeItem.ChildItems;
            SelectedNodeItem = NodeItems[index];
        }

        // Child nodes ///////////////////////////////////////////////////////////////////////////

        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(CanEditContent))]
        private XmlNodeItem[] childNodeItems = [];

        [ObservableProperty]
        private XmlNodeItem selectedChildNodeItem = null;

        [RelayCommand]
        public void GoTo()
        {
            if (SelectedChildNodeItem == null) return;
            var targetItem = SelectedChildNodeItem;
            NodeItems = ChildNodeItems;
            SelectedNodeItem = targetItem;
        }

        // Attributes ////////////////////////////////////////////////////////////////////////////

        [ObservableProperty]
        private XmlAttributeItem[] attributeItems = [];

        [ObservableProperty]
        private XmlAttributeItem selectedAttributeItem = null;

        [RelayCommand]
        private async Task EditAttributeBase64()
        {
            if (SelectedAttributeItem == null) return;
            var curValue = SelectedAttributeItem.Value;
            var dialog = new XmlEditorBase64(curValue, iconResourceName);
            await App.RunDialog(dialog);
            if (dialog.BoolResult) SelectedAttributeItem.Value = dialog.StringResult;
        }

        [RelayCommand]
        private async Task SelectSignal()
        {
            if (SelectedAttributeItem == null) return;
            var curValue = SelectedAttributeItem.Value;
            if (curValue != null && curValue.Split(':', StringSplitOptions.RemoveEmptyEntries).Length != 3) curValue = null;
            var config = await AgencyLocal.SelectSignal(new SignalConfig{ SignalID = curValue }, false, false, null);
            if (config != null && config.SignalID != null) SelectedAttributeItem.Value = config.SignalID;
        }

        [RelayCommand]
        private async Task SelectMessage()
        {
            if (SelectedAttributeItem == null) return;
            var curValue = SelectedAttributeItem.Value;
            if (curValue != null && curValue.Split(':', StringSplitOptions.RemoveEmptyEntries).Length != 2) curValue = null;
            var message = await AgencyLocal.SelectBusMessage(curValue);
            if (message != null) SelectedAttributeItem.Value = message;
        }

        // Content ///////////////////////////////////////////////////////////////////////////////

        [ObservableProperty]
        private String content = "";

        partial void OnContentChanged(String value)
        {
            if (SelectedNodeItem == null) return;
            SelectedNodeItem.Element.InnerText = value;
        }

        public bool CanEditContent => SelectedNodeItem != null && ChildNodeItems.Length == 0;

        [RelayCommand]
        private async Task EditContentBase64()
        {
            var dialog = new XmlEditorBase64(Content, iconResourceName);
            await App.RunDialog(dialog);
            if (dialog.BoolResult) Content = dialog.StringResult;
        }

        // Result ////////////////////////////////////////////////////////////////////////////////

        [RelayCommand]
        private void Cancel()
        {
            CloseRequested?.Invoke(this, EventArgs.Empty);
        }

        [RelayCommand]
        private void Confirm()
        {
            Confirmed = true;
            CloseRequested?.Invoke(this, EventArgs.Empty);
        }

        public bool Confirmed { get; private set; } = false;
        public XmlDocument Xml { get; private set; } = xml ?? null;

        public event EventHandler CloseRequested;
    }

    partial class XmlEditorBase64 : DialogPanel
    {
        public XmlEditorBase64() // For designer
        {
            InitializeComponent();
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
        }

        public XmlEditorBase64(String base64String, String iconResourceName = null)
        {
            InitializeComponent();
            SetResizableMode(400, 200, 600, 400);
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
            Icon = ResourceLoader.Load(iconResourceName)?.ToAvaloniaWindowIcon();
            Title = language["title"];

            String text = "";
            try { text = Encoding.UTF8.GetString(Convert.FromBase64String(base64String)); }
            catch (Exception) {}
            textBox.Text = text;

            buttonOK.Click += delegate
            {
                StringResult = Convert.ToBase64String(Encoding.UTF8.GetBytes(textBox.Text));
                BoolResult = true;
                Close();
            };
            buttonCancel.Click += delegate
            {
                Close();
            };
        }

        private LanguageSwitch language;
    }
}